package com.iwdnb.gkgz.common.utils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import com.alibaba.fastjson.JSON;

import cn.hutool.core.date.DateUtil;
import com.iwdnb.bmnf.common.utils.BeanConvertUtils;
import com.iwdnb.bmnf.common.utils.BooleanUtils;
import com.iwdnb.gkgz.common.model.dto.StockDayData;
import com.iwdnb.gkgz.common.model.dto.StockRegion;
import com.iwdnb.gkgz.common.model.dto.StrategyTradeDTO;
import com.iwdnb.gkgz.common.quota.BiasIndicator;
import com.iwdnb.gkgz.common.quota.BiasIndicator.BiasRegion;
import com.iwdnb.gkgz.common.quota.IndicatorUtils;
import com.iwdnb.gkgz.common.quota.KdjIndicator;
import com.iwdnb.gkgz.common.quota.KdjIndicator.Kdj;
import com.iwdnb.gkgz.common.quota.KdjIndicator.KdjRegion;
import com.iwdnb.gkgz.common.quota.MacdIndicator;
import com.iwdnb.gkgz.common.quota.MacdIndicator.MacdRegion;
import com.iwdnb.gkgz.common.quota.WRIndicator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import static com.iwdnb.gkgz.common.constant.GkgzConstants.ZS_CODE;

@Slf4j
public class StockRegionUtils {

    private static final String FOLLOW_LATEST_DATE = "2099-01-01";

    public static List<StrategyTradeDTO> calculateRegions(String code, List<StockDayData> stockDayDataList) {
        stockDayDataList = IndicatorUtils.fixDatetime(stockDayDataList);
        WRIndicator.calculateWR(stockDayDataList);
        //底背离
        MacdIndicator.calculateMACD(stockDayDataList);
        List<MacdRegion> macdDivergenceRegionList = MacdIndicator.caculateBottomDivergence(stockDayDataList);
        System.out.println("------macd底背离:" + JSON.toJSONString(macdDivergenceRegionList));
        System.out.println(macdDivergenceRegionList.size());
        //柱子值大于100
        List<MacdRegion> macdActiveRegionList = MacdIndicator.caculateActiveRegion(stockDayDataList);
        System.out.println("------macd柱子值大于100:" + JSON.toJSONString(macdActiveRegionList));
        System.out.println(macdActiveRegionList.size());
        //柱子值小于-100
        List<MacdRegion> macdNegativeRegionList = MacdIndicator.caculateNegativeRegion(stockDayDataList);
        System.out.println("------macd柱子值小于-100:" + JSON.toJSONString(macdNegativeRegionList));
        System.out.println(macdNegativeRegionList.size());
        //-------------------KDJ-------------------------
        List<Kdj> kdjList = KdjIndicator.calculateKdj(stockDayDataList);
        //System.out.println(JSON.toJSONString(kdjList));

        List<KdjRegion> regionList = KdjIndicator.getKdjRegionList(stockDayDataList);
        //底部区域
        List<KdjRegion> kdjLowRegionList = regionList.stream().filter(t -> BooleanUtils.isFalse(t.getUpFlag())).collect(
            Collectors.toList());
        KdjIndicator.setRemark(kdjLowRegionList, "kdjLow");
        System.out.println("-----kdj底部区域:" + JSON.toJSONString(kdjLowRegionList));
        System.out.println(kdjLowRegionList.size());
        //底背离
        List<KdjRegion> kdjDivergenceRegionList = KdjIndicator.caculateBottomDivergence(stockDayDataList,
            BigDecimalUtils.of(20));
        System.out.println("-----kdj底背离区域:" + JSON.toJSONString(kdjDivergenceRegionList));
        System.out.println(kdjDivergenceRegionList.size());
        //j值小于-10
        List<KdjRegion> kdjNegativeKdjRegionList = KdjIndicator.caculateJValueNegativeRegion(stockDayDataList);
        System.out.println("-----kdj-j值小于-10区域:" + JSON.toJSONString(kdjNegativeKdjRegionList));
        System.out.println(kdjNegativeKdjRegionList.size());

        //顶部区域
        List<KdjRegion> kdjHighRegionList = regionList.stream().filter(t -> BooleanUtils.isTrue(t.getUpFlag())).collect(
            Collectors.toList());
        KdjIndicator.setRemark(kdjHighRegionList, "kdjHigh");
        System.out.println("-----kdj顶部区域:" + JSON.toJSONString(kdjHighRegionList));
        System.out.println(kdjHighRegionList.size());
        //j值大于100
        List<KdjRegion> kdjActiveRegionList = KdjIndicator.caculateJValueActiveRegion(stockDayDataList);

        System.out.println("-----kdj-j值大于100:" + JSON.toJSONString(kdjActiveRegionList));
        System.out.println(kdjActiveRegionList.size());

        //List<KdjRegion> filterKdjRegionList = BeanConvertUtils.convertList(allLowKdjLowRegionList, KdjRegion.class);
        //filterKdjRegionList.addAll(kdjHighRegionList);
        //
        //Collections.sort(filterKdjRegionList, Comparator.comparing(t -> t.getBegin().getDatetime()));
        //System.out.println("-----kdj过滤后全部顶底区域:" + JSON.toJSONString(filterKdjRegionList));
        //System.out.println(filterKdjRegionList.size());
        List<BiasRegion> biasRegionList = BiasIndicator.getBiasRegionList(stockDayDataList, 30, 60);
        System.out.println("-----bias-顶部区域:" + JSON.toJSONString(biasRegionList));
        System.out.println(biasRegionList.size());

        List<StockRegion> stockRegionList = new ArrayList<>();

        //StockRegionUtils.addMacdRegions(stockRegionList, stockDayDataList, macdActiveRegionList);

        StockRegionUtils.addKdjRegions(stockRegionList, stockDayDataList, kdjHighRegionList);
        StockRegionUtils.addKdjRegions(stockRegionList, stockDayDataList, kdjActiveRegionList);

        StockRegionUtils.addKdjRegions(stockRegionList, stockDayDataList, kdjLowRegionList);
        StockRegionUtils.addKdjRegions(stockRegionList, stockDayDataList, kdjDivergenceRegionList);
        StockRegionUtils.addKdjRegions(stockRegionList, stockDayDataList, kdjNegativeKdjRegionList);

        //StockRegionUtils.addMacdRegions(stockRegionList, stockDayDataList, macdDivergenceRegionList);
        StockRegionUtils.addMacdRegions(stockRegionList, stockDayDataList, macdNegativeRegionList);

        StockRegionUtils.addBiasRegions(stockRegionList, stockDayDataList, biasRegionList);

        System.out.println("-----全部顶底区域:" + JSON.toJSONString(stockRegionList));
        System.out.println(stockRegionList.size());

        List<StrategyTradeDTO> strategyTradeDTOList = new ArrayList<>();
        //计算盈亏
        StockDayData buyData = null;
        StrategyTradeDTO strategyTradeDTO = null;

        for (int i = 0; i < stockRegionList.size(); i++) {
            StockRegion stockRegion = stockRegionList.get(i);
            StockRegion nextStockRegion = i == stockRegionList.size() - 1 ? null : stockRegionList.get(i + 1);
            String operateDate = StockRegionUtils.getOperateDate(stockRegion);
            if (StringUtils.equals(operateDate, FOLLOW_LATEST_DATE)) {
                operateDate = "跟随最新数据后续进行操作";
            }
            StockDayData data = StockUtils.getStockDayData(stockDayDataList, operateDate);
            if (Objects.isNull(data)) {
                log.error("{}-stockData is null,ignore.", operateDate);
                continue;
            }
            if (buyData == null) {
                if (BooleanUtils.isFalse(stockRegion.getUpFlag())) {
                    buyData = data;
                    strategyTradeDTO = new StrategyTradeDTO();
                    strategyTradeDTO.setUuid(ZS_CODE);
                    strategyTradeDTO.setCode(code);
                    strategyTradeDTO.setBuyDate(DateUtil.parse(buyData.getDate()));
                    strategyTradeDTO.setBuyPrice(buyData.getClosePrice());
                    strategyTradeDTO.setBuyMinPrice(buyData.getMinPrice());
                    strategyTradeDTO.setStatus("buy");
                    strategyTradeDTO.setSummary(stockRegion.getRemark());
                    strategyTradeDTOList.add(strategyTradeDTO);
                    boolean flag = checkStopLoss(stockDayDataList, buyData, stockRegion, nextStockRegion,
                        strategyTradeDTO);
                    if (flag) {
                        buyData = null;
                        strategyTradeDTO = null;
                    }

                } else {
                    log.info("{}-顶部区域,价格:{},前面没有买点信息,忽略,当前策略:{}", operateDate, data.getClosePrice(),
                        stockRegion.getRemark());
                }
                continue;
            } else {
                if (BooleanUtils.isFalse(stockRegion.getUpFlag())) {
                    boolean flag = checkStopLoss(stockDayDataList, buyData, stockRegion, nextStockRegion,
                        strategyTradeDTO);
                    if (flag) {
                        buyData = null;
                        strategyTradeDTO = null;
                    } else {
                        log.info("{}-底部区域,价格:{},前面已存在买点信息,忽略,买入策略:{}", operateDate, data.getClosePrice(),
                            strategyTradeDTO.getSummary());
                    }
                } else {
                    BigDecimal rate = BigDecimalUtils.subStractAndDividePrecent(data.getClosePrice(),
                        buyData.getClosePrice());
                    String sellInfo = String.format("%s~%s:买入价:%s,卖出价:%s,盈利:%s,买入策略:%s,卖出策略:%s", buyData.getDate(),
                        operateDate, buyData.getClosePrice().toString(), data.getClosePrice().toString(),
                        rate.toString(), strategyTradeDTO.getSummary(), stockRegion.getRemark());
                    log.info(sellInfo);
                    setSelllInfo(strategyTradeDTO, buyData, data, sellInfo);
                    buyData = null;
                }
            }
            //System.out.println(operateDate + ":" + info);
        }
        if (Objects.nonNull(buyData)) {
            log.info("{}-最新买入,买入价:{},等待卖出信号,买入策略:{}", buyData.getDate(), buyData.getClosePrice(),
                strategyTradeDTO.getSummary());
        }
        return strategyTradeDTOList;
    }

    private static boolean checkStopLoss(List<StockDayData> stockDayDataList, StockDayData data,
        StockRegion stockRegion, StockRegion nextStockRegion,
        StrategyTradeDTO strategyTradeDTO) {
        if (Objects.isNull(nextStockRegion)) {
            return false;
        }
        Date date = DateUtil.parse(data.getDate());
        Date beginDate = WorkDayUtils.offsetWorkDay(date, -5);
        Date endDate = WorkDayUtils.offsetWorkDay(date, 5);
        List<StockDayData> subList = StockUtils.getStockDayDataListByDateRange(stockDayDataList, beginDate, endDate);
        StockDayData minData = StockUtils.getMinPriceData(subList);
        BigDecimal closePrice = data.getClosePrice();
        BigDecimal minPrice = minData.getMinPrice();
        BigDecimal stopLossPrice = minPrice;
        BigDecimal fiveDayStopLossPrice = BigDecimalUtils.multiply(data.getMinPrice(),
            BigDecimalUtils.zeroPointNineSix);
        stockDayDataList = StockUtils.getStockDayDataListByDateRange(stockDayDataList, data.getDate(),
            nextStockRegion.getLimitDate());
        for (int i = 0; i < stockDayDataList.size(); i++) {
            StockDayData stockDayData = stockDayDataList.get(i);

            if (i < 5) {
                if (BigDecimalUtils.isLt(stockDayData.getClosePrice(), fiveDayStopLossPrice)) {
                    BigDecimal stopPrice = stockDayData.getClosePrice();
                    BigDecimal rate = BigDecimalUtils.subStractAndDividePrecent(stopPrice, closePrice);
                    String sellInfo = String.format("%s~%s:买入后前5日第%s日止损,买入价:%s,止损价:%s,收益:%s,买入策略:%s,卖出策略:%s",
                        data.getDate(),
                        stockDayData.getDate(), i + 1 + "", closePrice.toString(), stopPrice.toString(),
                        rate.toString(), strategyTradeDTO.getSummary(), stockRegion.getRemark());
                    log.info(sellInfo);
                    setSelllInfo(strategyTradeDTO, data, stockDayData, sellInfo);
                    return true;
                }
            } else {
                if (i < 10 && BigDecimalUtils.isLt(stockDayData.getRate(), BigDecimalUtils.fuTwo)) {
                    BigDecimal stopPrice = stockDayData.getClosePrice();
                    BigDecimal rate = BigDecimalUtils.subStractAndDividePrecent(stopPrice, closePrice);
                    String sellInfo = String.format("%s~%s:买入后大跌于第%s日止损,买入价:%s,止损价:%s,收益:%s,买入策略:%s,卖出策略:%s",
                        data.getDate(),
                        stockDayData.getDate(), i + 1 + "", closePrice.toString(), stopPrice.toString(),
                        rate.toString(), strategyTradeDTO.getSummary(), stockRegion.getRemark());
                    log.info(sellInfo);
                    setSelllInfo(strategyTradeDTO, data, stockDayData, sellInfo);
                    return true;
                }
                if (BigDecimalUtils.isLt(stockDayData.getClosePrice(), stopLossPrice)) {
                    BigDecimal stopPrice = stockDayData.getClosePrice();
                    BigDecimal rate = BigDecimalUtils.subStractAndDividePrecent(stopPrice, closePrice);
                    String sellInfo = String.format("%s~%s:买入后第%s日止损,买入价:%s,止损价:%s,收益:%s,买入策略:%s,卖出策略:%s",
                        data.getDate(),
                        stockDayData.getDate(), i + 1 + "", closePrice.toString(), stopPrice.toString(),
                        rate.toString(), strategyTradeDTO.getSummary(), stockRegion.getRemark());
                    log.info(sellInfo);
                    setSelllInfo(strategyTradeDTO, data, stockDayData, sellInfo);
                    return true;
                }
            }
        }
        return false;
    }

    private static void setSelllInfo(StrategyTradeDTO strategyTradeDTO, StockDayData buyData, StockDayData sellData,
        String sellInfo) {
        BigDecimal sellPrice = sellData.getClosePrice();
        BigDecimal rate = BigDecimalUtils.subStractAndDividePrecent(sellPrice, buyData.getClosePrice());
        strategyTradeDTO.setStatus("sell");
        strategyTradeDTO.setHoldDay(WorkDayUtils.getWorkDayList(buyData.getDate(), sellData.getDate()).size());
        strategyTradeDTO.setSellDate(DateUtil.parseDate(sellData.getDate()));
        strategyTradeDTO.setSellPrice(sellPrice);
        strategyTradeDTO.setProfitRate(rate);
        strategyTradeDTO.setWinFlag(BigDecimalUtils.isGt(rate, BigDecimal.ZERO) ? "y" : "n");
        strategyTradeDTO.setFollowInfo(sellInfo);
    }

    public static void addKdjRegions(List<StockRegion> regionList, List<StockDayData> stockDayDataList,
        List<KdjRegion> regions) {
        for (KdjRegion kdjRegion : regions) {
            StockRegion stockRegion = new StockRegion();
            stockRegion.setBeginDate(kdjRegion.getBegin().getDatetime().replaceAll(" 00:00:00", ""));
            stockRegion.setEndDate(kdjRegion.getEnd().getDatetime().replaceAll(" 00:00:00", ""));
            stockRegion.setLimitDate(kdjRegion.getLimit().getDatetime().replaceAll(" 00:00:00", ""));
            stockRegion.setSignalType("kdj");
            stockRegion.setUpFlag(kdjRegion.getUpFlag());
            stockRegion.setRemark(kdjRegion.getRemark());
            stockRegion.setRegionValue(kdjRegion.getRegionValue());
            stockRegion.setOperateInLimitIndex(kdjRegion.getOperateInLimitIndex());
            init(regionList, stockDayDataList, stockRegion, false);
        }
        Collections.sort(regionList, Comparator.comparing(t -> t.getBeginDate()));

    }

    public static void addMacdRegions(List<StockRegion> regionList, List<StockDayData> stockDayDataList,
        List<MacdRegion> regions) {
        for (MacdRegion region : regions) {
            StockRegion stockRegion = new StockRegion();
            stockRegion.setBeginDate(region.getBegin().getDatetime().replaceAll(" 00:00:00", ""));
            stockRegion.setEndDate(region.getEnd().getDatetime().replaceAll(" 00:00:00", ""));
            stockRegion.setLimitDate(region.getLimit().getDatetime().replaceAll(" 00:00:00", ""));
            stockRegion.setSignalType("macd");
            stockRegion.setUpFlag(region.getUpFlag());
            stockRegion.setRegionValue(region.getRegionValue());
            stockRegion.setRemark(region.getRemark());
            stockRegion.setOperateInLimitIndex(region.getOperateInLimitIndex());

            init(regionList, stockDayDataList, stockRegion, false);
        }
        Collections.sort(regionList, Comparator.comparing(t -> t.getBeginDate()));
    }

    private static void addBiasRegions(List<StockRegion> regionList, List<StockDayData> stockDayDataList,
        List<BiasRegion> regions) {
        for (BiasRegion region : regions) {
            StockRegion stockRegion = new StockRegion();
            stockRegion.setBeginDate(region.getBegin().getDatetime().replaceAll(" 00:00:00", ""));
            stockRegion.setEndDate(region.getEnd().getDatetime().replaceAll(" 00:00:00", ""));
            stockRegion.setLimitDate(region.getLimit().getDatetime().replaceAll(" 00:00:00", ""));
            stockRegion.setSignalType("bias");
            stockRegion.setUpFlag(region.getUpFlag());
            stockRegion.setRegionValue(region.getRegionValue());
            stockRegion.setRemark(region.getRemark());
            stockRegion.setOperateInLimitIndex(region.getOperateInLimitIndex());

            init(regionList, stockDayDataList, stockRegion, false);
        }
        Collections.sort(regionList, Comparator.comparing(t -> t.getBeginDate()));
    }

    private static void init(List<StockRegion> regionList, List<StockDayData> stockDayDataList,
        StockRegion stockRegion, boolean forceUpdateFlag) {
        stockRegion.setBeginValue(
            StockUtils.getStockDayData(stockDayDataList, stockRegion.getBeginDate()).getClosePrice());
        List<StockDayData> dataList = StockUtils.getStockDayDataListByDateRange(stockDayDataList,
            stockRegion.getBeginDate(), stockRegion.getEndDate());
        if (BooleanUtils.isTrue(stockRegion.getUpFlag())) {
            BigDecimal limitValue = StockUtils.getMaxPriceData(dataList).getClosePrice();
            stockRegion.setLimitValue(limitValue);
        } else {
            BigDecimal limitValue = StockUtils.getMinPriceData(dataList).getClosePrice();
            stockRegion.setLimitValue(limitValue);
        }
        if (validate(regionList, stockRegion, dataList) && !containsRegion(regionList, stockRegion, forceUpdateFlag)) {
            regionList.add(stockRegion);
        }
    }

    private static boolean validate(List<StockRegion> regionList, StockRegion stockRegion,
        List<StockDayData> dataList) {

        regionList = regionList.stream().filter(
                t -> DateUtils.after(DateUtil.parseDate(stockRegion.getBeginDate()),
                    DateUtil.parseDate(t.getEndDate())))
            .collect(Collectors.toList());
        Collections.sort(regionList, Comparator.comparing(t -> t.getBeginDate()));
        StockRegion lastRegion = regionList.size() > 0 ? regionList.get(regionList.size() - 1) : null;
        if (Objects.isNull(lastRegion)) {
            return true;
        }
        List<Date> dateList = WorkDayUtils.getWorkDayList(DateUtil.parseDate(lastRegion.getEndDate()),
            DateUtil.parseDate(stockRegion.getBeginDate()));
        //如果间隔过大,没有参考意义，返回true
        if (dateList.size() > 30) {
            return true;
        }
        //找到前低的区间
        StockRegion lastSecondRegion = regionList.size() > 1 ? regionList.get(regionList.size() - 2) : null;
        if (Objects.isNull(lastSecondRegion)) {
            return true;
        }

        dateList = WorkDayUtils.getWorkDayList(DateUtil.parseDate(lastSecondRegion.getEndDate()),
            DateUtil.parseDate(stockRegion.getBeginDate()));
        //如果间隔过大,没有参考意义，返回true
        if (dateList.size() > 60) {
            return true;
        }
        //如果上一个区间和当前区间是同一个方向的，则不添加
        if (StringUtils.equals(lastRegion.getUpFlag(), stockRegion.getUpFlag())) {
            return true;
        }

        return true;
    }

    private static boolean containsRegion(List<StockRegion> regionList, StockRegion stockRegion,
        boolean forceUpdateFlag) {
        for (StockRegion region : regionList) {
            if (StringUtils.equals(region.getUpFlag(), stockRegion.getUpFlag())
                && DateUtils.before(region.getBeginDate(), stockRegion.getEndDate())
                && DateUtils.before(stockRegion.getBeginDate(), region.getEndDate())) {
                //如果需要强制更新区间(bias使用),且新区间开始时间在已存在区间内，则更新区间
                if (forceUpdateFlag &&
                    DateUtils.beforeOrEquals(stockRegion.getBeginDate(), region.getLimitDate())) {
                    BeanConvertUtils.copyProperties(stockRegion, region);
                    return true;
                }
                //设置下备注、是否当天操作
                region.setRemark(region.getRemark() + "," + stockRegion.getRemark());
                //比较区间，如果极点时间在已存在区间之前，设置原区间开始时间改为极值时间前
                if (DateUtils.before(stockRegion.getLimitDate(), region.getEndDate())) {
                    Date date = WorkDayUtils.offsetWorkDay(stockRegion.getLimitDate(), -1);
                    region.setBeginDate(DateUtil.formatDate(date));
                }
                //比较区间，如果极点时间在已存在区间后，设置区间不相交，并把开始时间改为存在的结束时间后
                if (DateUtils.after(stockRegion.getLimitDate(), region.getEndDate())) {
                    Date date = WorkDayUtils.offsetWorkDay(region.getEndDate(), 1);
                    stockRegion.setBeginDate(DateUtil.formatDate(date));
                    return containsRegion(regionList, stockRegion, forceUpdateFlag);
                }
                //比较区间的极值时间在区间时间段内,且操作时间在原值之前，替换
                if (DateUtils.afterOrEquals(stockRegion.getLimitDate(), region.getBeginDate())
                    && DateUtils.before(getOperateDate(stockRegion), getOperateDate(region))) {
                    region.setLimitDate(stockRegion.getLimitDate());
                    region.setOperateInLimitIndex(stockRegion.getOperateInLimitIndex());
                    //比较加入的区间，如果开始时间在已存在区间前，把已存在区间的开始时间设置提前
                    if (DateUtils.before(stockRegion.getBeginDate(), region.getBeginDate())) {
                        region.setBeginDate(stockRegion.getBeginDate());
                    }
                }

                return true;
            }
        }
        return false;
    }

    public static String getOperateDate(StockRegion stockRegion) {
        String operateDate = stockRegion.getLimitDate();
        if (stockRegion.getOperateInLimitIndex() == -1) {
            return FOLLOW_LATEST_DATE;
        }
        if (stockRegion.getOperateInLimitIndex() > 0) {
            operateDate = DateUtil.formatDate(
                WorkDayUtils.offsetWorkDay(DateUtil.parseDate(operateDate), stockRegion.getOperateInLimitIndex()));
        }
        return operateDate;
    }
}
